use colored::Colorize;
use log::info;
use rouille::Request;
use rouille::Response;
use serde_json::{json, Value};
use std::io::Read;
use std::str;
use std::sync::Mutex;

use super::broadcast;
use super::check::SystemInfoAndBuild;
use super::client::{json_to, Reply};
use super::config::get_config_password;
use super::encrypt::{Cryptor, Encryptor};
use super::utils::{now, OutErr};

lazy_static! {
    static ref CRYPTOR: Mutex<Encryptor> = {
        let pwd = get_config_password();
        let enc = Encryptor::new(&pwd);
        Mutex::new(enc)
    };
}

pub trait ValueFroBrd {
    fn to_brd(&self) -> String;
}

pub fn with_json<F>(request: &Request, handle: F) -> Response
where
    F: Fn(&Value) -> OutErr<String>,
{
    let mut data = request.data().expect(
        "Oops, body already retrieved, problem \
         in the server",
    );
    let mut buf = Vec::new();
    match data.read_to_end(&mut buf) {
        Ok(_) => (),
        Err(_) => return Response::text("Failed to read body"),
    };
    let val: Value = serde_json::from_str(
        str::from_utf8_mut(&mut buf).expect("trans to str err from raw bytes"),
    )
    .expect("this is not json ");

    match handle(&val) {
        Ok(t) => Response::text(t),
        Err(e) => return Response::text(format!("Failed to read body: {}", e)),
    }
}
macro_rules! _S {
    ($value:ident [$name:tt]) => {
        match $value[$name].as_str() {
            Some(r) => r,
            _ => {
                return format!("lack key:{} ", &stringify!($name));
            }
        }
    };
}

pub struct Parameter {
    pub op: String,
    pub data: String,
    pub data_tp: String,
    pub brd: String,
    pub encrypt: String,
    host: String,
}

impl Parameter {
    // pub fn get_args(&self) -> (String, String, String){
    //     (self.op.to_string(), self.data.to_string(), self.data_tp.to_string() )
    // }

    pub fn get_args_str(&self) -> (&str, &str, &str) {
        (&self.op, &self.data, &self.data_tp)
    }

    pub fn get_op(&self) -> String {
        self.op.to_string()
    }

    pub fn json_client(&self, uri: &str, encrypt: Option<&Encryptor>) -> Reply {
        let brd = if self.brd == "lack key" { false } else { true };

        json_to(
            &self.op,
            &self.data,
            &self.data_tp,
            uri,
            Some(&self.host),
            encrypt,
            Some(brd),
        )
    }
}

impl Default for Parameter {
    fn default() -> Self {
        Self {
            op: "ls".to_string(),
            data: "".to_string(),
            data_tp: "".to_string(),
            brd: "lack key".to_string(),
            encrypt: "lack key".to_string(),
            host: "http://localhost:8000".to_string(),
        }
    }
}

impl std::fmt::Display for Parameter {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let data_len = self.data.len();
        if data_len > 20 {
            write!(
                f,
                "{} : {}...({}) [{}]",
                self.op.green(),
                &self.data[..20],
                data_len,
                self.data_tp.yellow()
            )
        } else {
            write!(
                f,
                "{} : {} [{}]",
                self.op.green(),
                self.data,
                self.data_tp.yellow()
            )
        }
    }
}


pub fn protocol<F>(value: &Value, sysinfo: &SystemInfoAndBuild, operation: F) -> String
where
    F: Fn(&Parameter, &SystemInfoAndBuild) -> (i32, String),
{
    let op: &str = _S!(value["op"]);
    let mut data: &str = _S!(value["data"]);
    let tp: &str = _S!(value["data_tp"]);
    let if_encry = match value["encrypt"].as_str() {
        Some(x) => x,
        _ => "lack key",
    };

    let if_brd = match value["brd"].as_str() {
        Some(x) => x,
        _ => "lack key",
    };
    let cipher = CRYPTOR.lock().expect("unlock cryptor failed");

    let de_data: String;
    if !if_encry.contains("lack key") {
        de_data = match String::from_utf8(cipher.de_b64(data)) {
            Ok(s) => s,
            _ => {
                return String::from("password is error");
            }
        };
        // .expect("decrypt utf8 failed");
        data = &de_data;
    }

    let parameter = Parameter {
        op: op.to_string(),
        data: data.to_string(),
        data_tp: tp.to_string(),
        ..Parameter::default()
    };
    info!("parameter: {}", &parameter);

    if if_brd != "lack key" {
        // if if_brd != "lack key" && if_brd.starts_with("/"){
        let para = Parameter {
            op: format!("brd[{}:{}]", parameter.op, if_brd),
            data: data.to_string(),
            data_tp: parameter.data_tp.to_string(),
            ..Parameter::default()
        };
        // let (code, msg) = redirect_to_brd(if_brd, &para);
        let (code, mut msg) = broadcast::broadcast(&para);
        if if_encry != "lack key" {
            msg = cipher.en_b64(msg.as_bytes());
        }
        json!({
            "status": code,
            "msg": msg,
            "time": now()
        })
        .to_string()
    } else {
        let (code, mut msg) = operation(&parameter, sysinfo);
        if if_encry != "lack key" {
            msg = cipher.en_b64(msg.as_bytes());
        }
        json!({
            "status": code,
            "msg": msg,
            "time": now()
        })
        .to_string()
    }
}

pub fn with_parameter<F>(request: &Request, sysinfo: &SystemInfoAndBuild, oper: F) -> Response
where
    F: Fn(&Parameter, &SystemInfoAndBuild) -> (i32, String),
{
    with_json(request, |para| Ok(protocol(para, sysinfo, &oper)))
}
